package com.jswitch.server.transaction;

import com.jswitch.common.enums.TransactionStateEnum;
import com.jswitch.server.cache.SipDialogManageCache;
import com.jswitch.server.cache.TransactionManageCache;
import com.jswitch.server.msg.SipMessageEvent;
import com.jswitch.sip.*;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.Objects;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author danmo
 * @date 2024-07-02 14:26
 **/
@Slf4j
@Data
public class ServerSipTransaction implements SipTransaction {

    /**
     * TU
     */
    private TransactionUser transactionUser;

    /**
     * 状态
     */
    private TransactionStateEnum state;

    /**
     * 最后一次请求
     */
    private Request lastRequest;

    /**
     * 最后一个临时响应
     */
    private Response lastProvisionalResponse;
    /**
     * 最后一次响应
     */
    private Response lastFinalResponse;


    private String transactionId;

    /**
     * 事件消息
     */
    private SipMessageEvent eventMsg;

    private SipDialog sipDialog;


    private final long T1 = 500;  // T1 默认值为500ms
    private final long T2 = 4000; // T2 默认值为4s
    private final long T4 = 5000; // T4 默认值为5s

    private ScheduledThreadPoolExecutor timerTemporary;
    private ScheduledThreadPoolExecutor timerG;
    private ScheduledThreadPoolExecutor timerH;
    private ScheduledThreadPoolExecutor timerI;
    private ScheduledThreadPoolExecutor timerJ;

    public ServerSipTransaction(SipMessageEvent event, TransactionUser transactionUser) {
        this.state = TransactionStateEnum.INITIAL;
        this.eventMsg = event;
        this.transactionUser = transactionUser;
        this.transactionId = event.getMessage().getTransactionId();
    }

    private void handleInviteRequest(SipRequest sipRequest) {
        if (state == TransactionStateEnum.INITIAL) {
            state = TransactionStateEnum.PROCEEDING;
            lastRequest = sipRequest;
            scheduleProvisionalResponse(sipRequest);

            if (StringUtils.isEmpty(sipRequest.getToTag())) {
                // 对 To 头域没有 tag 的处理（视具体需求而定）
            }
            transactionUser.sendRequest(sipRequest, eventMsg.getCtx());
        } else if (state == TransactionStateEnum.PROCEEDING && isRetransmission(eventMsg)) {
            resendLastProvisionalResponse();
        }
    }

    private void handleAckRequest(SipRequest sipRequest) {
        if (state == TransactionStateEnum.COMPLETED) {
            state = TransactionStateEnum.CONFIRMED;
            shutdownTimer(timerG);

            scheduleTermination(sipRequest, T4);
        }
    }

    private void handleNonInviteRequest(SipRequest sipRequest) {
        if (state == TransactionStateEnum.INITIAL) {
            state = TransactionStateEnum.TRYING;
            lastRequest = sipRequest;
            transactionUser.sendRequest(sipRequest, eventMsg.getCtx());

            lastProvisionalResponse = sendResponse(sipRequest.createResponse(SipResponse.TRYING));
            lastFinalResponse = transactionUser.generateFinalResponse(sipRequest);
            sendFinalResponse();

            if (isErrorResponse(lastFinalResponse)) {
                state = TransactionStateEnum.COMPLETED;
                startTimerJ(sipRequest);
            }
        } else if (state == TransactionStateEnum.PROCEEDING && isRetransmission(eventMsg)) {
            resendLastProvisionalResponse();
        } else if (state == TransactionStateEnum.COMPLETED && isRetransmission(eventMsg)) {
            resendLastFinalResponse();
        }
    }

    @Override
    public synchronized Response sendResponse(Response response) {
        SipRequest sipRequest = (SipRequest) eventMsg.getMessage();
        if (response instanceof SipResponse sipResponse) {
            if (sipResponse.getStatusCode() >= 200 && sipResponse.getStatusCode() < 300) {
                this.state = TransactionStateEnum.TERMINATED;
                // 停止定时任务
                timerTemporary.shutdownNow();
                sendToTransportLayer(eventMsg.getCtx(), sipResponse);
                closeTransaction(sipRequest.getTransactionId());

                if (Objects.isNull(sipDialog) && sipResponse.getStatusCode() == 200) {
                    if (!SipDialogManageCache.isExist(sipRequest.getDialogId(true))) {
                        SipDialog dialog = SipDialog.createDialogFromResponse((SipResponse) response, sipRequest);
                        SipDialogManageCache.saveSipDialog(dialog);
                    } else {
                        SipDialog sipDialog = SipDialogManageCache.getSipDialog(sipRequest.getDialogId(true));
                        sipDialog.setState(DialogState.COMPLETED);
                    }
                }
            } else if (sipResponse.getStatusCode() >= 300 && sipResponse.getStatusCode() < 700) {
                if (this.state == TransactionStateEnum.PROCEEDING) {
                    this.lastFinalResponse = sipResponse;
                    sendToTransportLayer(eventMsg.getCtx(), sipResponse);
                    this.state = TransactionStateEnum.COMPLETED;
                    if (!isReliableTransport(sipResponse)) {
                        timerG = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("serverTransaction timerG"));
                        timerG.schedule(new Runnable() {
                            @Override
                            public void run() {
                                sendToTransportLayer(eventMsg.getCtx(), lastFinalResponse);
                                long nextG = Math.min(2 * T1, T2);
                                timerG.schedule(this, nextG, TimeUnit.MILLISECONDS);
                            }
                        }, T1, TimeUnit.MILLISECONDS);
                    }

                    if (!SipDialogManageCache.isExist(sipRequest.getDialogId(true))) {
                        SipDialogManageCache.removeSipDialog(sipRequest.getDialogId(true));
                    }

                    timerH = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("serverTransaction TimeH"));
                    timerH.schedule(new Runnable() {
                        @Override
                        public void run() {
                            if (state == TransactionStateEnum.COMPLETED) {
                                state = TransactionStateEnum.TERMINATED;
                                transactionUser.sendResponseError(new SipMessageEvent(sipResponse, eventMsg.getCtx()));
                                closeTransaction(sipRequest.getTransactionId());
                            } else if (state == TransactionStateEnum.TERMINATED) {
                                timerH.shutdownNow();
                                closeTransaction(sipRequest.getTransactionId());
                            }
                        }
                    }, 64 * T1, TimeUnit.MILLISECONDS);
                }
            } else {
                if (this.state == TransactionStateEnum.PROCEEDING) {
                    lastProvisionalResponse = sipResponse;
                    sendToTransportLayer(eventMsg.getCtx(), sipResponse);
                }

                if (!SipDialogManageCache.isExist(sipRequest.getDialogId(true))) {
                    SipDialog dialog = SipDialog.createDialogFromResponse(sipResponse, sipRequest);
                    SipDialogManageCache.saveSipDialog(dialog);
                }
            }
        }
        return response;
    }

    private void sendFinalResponse() {
        if (lastFinalResponse != null) {
            state = isSuccessResponse(lastFinalResponse) ? TransactionStateEnum.TERMINATED : TransactionStateEnum.COMPLETED;
            sendToTransportLayer(eventMsg.getCtx(), lastFinalResponse);
            closeTransactionIfNeeded();
        }
    }

    private void scheduleProvisionalResponse(SipRequest sipRequest) {
        timerTemporary = createAndScheduleTimer(200, () -> {
            if (state == TransactionStateEnum.PROCEEDING) {
                lastProvisionalResponse = sendResponse(sipRequest.createResponse(SipResponse.TRYING));
            }
        });
    }

    private void scheduleTermination(SipRequest sipRequest, long delay) {
        timerI = createAndScheduleTimer(delay, () -> {
            state = TransactionStateEnum.TERMINATED;
            closeTransaction(sipRequest.getTransactionId());
        });
    }

    private ScheduledThreadPoolExecutor createAndScheduleTimer(long delay, Runnable task) {
        ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("serverTransactionTimer"));
        timer.schedule(task, delay, TimeUnit.MILLISECONDS);
        return timer;
    }

    private void resendLastProvisionalResponse() {
        if (lastProvisionalResponse != null) {
            sendToTransportLayer(eventMsg.getCtx(), lastProvisionalResponse);
        }
    }

    private void resendLastFinalResponse() {
        if (lastFinalResponse != null) {
            sendToTransportLayer(eventMsg.getCtx(), lastFinalResponse);
        }
    }

    private void startTimerJ(SipRequest request) {
        long delay = isReliableTransport(request) ? 0 : 64 * T1;
        timerJ = createAndScheduleTimer(delay, () -> {
            state = TransactionStateEnum.TERMINATED;
            closeTransaction(request.getTransactionId());
        });
    }

    private boolean isRetransmission(SipMessageEvent event) {
        return lastRequest != null && lastRequest.equals(event.getMessage());
    }

    private boolean isReliableTransport(SipMessage sipMessage) {
        String transport = sipMessage.getTopmostVia().getTransport();
        return "TCP".equalsIgnoreCase(transport) || "TLS".equalsIgnoreCase(transport);
    }

    private boolean isSuccessResponse(Response response) {
        return response.getStatusCode() >= 200 && response.getStatusCode() < 300;
    }

    private boolean isErrorResponse(Response response) {
        return response.getStatusCode() >= 300;
    }

    private void sendToTransportLayer(ChannelHandlerContext ctx, Response response) {
        log.info("send response: {}", response);
        ctx.writeAndFlush(response);
    }

    private void closeTransaction(String transactionId) {
        shutdownTimers();
        TransactionManageCache.removeServerTransaction(transactionId);
    }

    private void closeTransactionIfNeeded() {
        if (state == TransactionStateEnum.TERMINATED) {
            closeTransaction(transactionId);
        }
    }

    private void shutdownTimers() {
        shutdownTimer(timerTemporary);
        shutdownTimer(timerG);
        shutdownTimer(timerH);
        shutdownTimer(timerI);
        shutdownTimer(timerJ);
    }

    private void shutdownTimer(ScheduledThreadPoolExecutor timer) {
        if (timer != null && !timer.isShutdown()) {
            timer.shutdownNow();
        }
    }

    @Override
    public void start() {
        // Implement if needed
    }

    @Override
    public void terminate() {
        closeTransaction(transactionId);
    }

    @Override
    public void cancel() {
        terminate();
    }

    @Override
    public void timeout() {
        terminate();
    }

    @Override
    public void processRequest(SipRequest request) {
        switch (request.getMethod()) {
            case Request.INVITE -> handleInviteRequest(request);
            case Request.ACK -> handleAckRequest(request);
            default -> handleNonInviteRequest(request);
        }
    }

    @Override
    public void processResponse(SipResponse response) {
        // Server-side transactions generally do not handle responses
    }

    @Override
    public SipDialog getDialog() {
        return sipDialog;
    }

    @Override
    public boolean matches(SipRequest request) {
        return transactionId.equals(request.getTransactionId());
    }
}
